package it.francescogabbrielli.android.solidalapp;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

import it.francescogabbrielli.android.solidalapp.crashendpoint.Crashendpoint;
import it.francescogabbrielli.android.solidalapp.crashendpoint.model.Crash;

import com.google.api.client.json.GenericJson;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;

/**
 * Abstract action. 
 */
public abstract class Action {
	
	protected Context context;
	protected SolidalApp app;
	protected SharedPreferences prefs;
	protected Intent intent;
	
	private String action;
	
	private long timestamp;

	/**
	 * Init action
	 * 
	 * @param context
	 * 		the execution context
	 * @param intent
	 * 		the intent the started the action
	 * @return
	 * 		this
	 */
	Action init(Context context, Intent intent) {
		this.context = context;
		this.intent = intent;
		this.intent.setComponent(null);
		action = getClass().getSimpleName();
		app = (SolidalApp) context.getApplicationContext();
		prefs = context.getSharedPreferences(
				SolidalApp.class.getCanonicalName(), Context.MODE_PRIVATE);
		timestamp = System.currentTimeMillis();
		return this;
	}
	
	public long getTimestamp() {
		return timestamp;
	}
	
	public SolidalApp getApp() {
		return app;
	}
	
	public Intent getIntent() {
		return intent;
	}
	
	public synchronized String getAction() {
		return action;
	}
	
	public synchronized Action setAction(String action) {
		this.action = action;
		return this;
	}
	
	protected final void setModel(GenericJson model) {
		if (model!=null)
			intent.putExtras(EndpointsUtils.toBundle(model));
	}
	
	protected final <T extends GenericJson> T getModel(Class<T> modelClass) {
		return EndpointsUtils.fromBundle(intent.getExtras(), modelClass); 
	}
		
	/**
	 * Execute a remote action
	 * 		
	 * @throws Exception
	 * 		if not catched
	 */
	public abstract void execute() throws Exception;
	
	public void onError(String error) {
		app.alert(context.getString(R.string.error_server, error));
	}
		
	/**
	 * Use the results of the remote action
	 * 		
	 * @throws Exception
	 * 		if not catched
	 */
	public abstract void onPostExecute() throws Exception;
	
	public static boolean check(Class<? extends Action> ac, Intent intent) {
		return ac.getSimpleName().equals(intent.getAction());
	}
	
	/**
	 * Start the action
	 * 
	 * @param context
	 *		the current context
	 * @param actionClass
	 * 		the action class
	 */ 
	public static void start(Context context, Class<? extends Action> actionClass) {
		start(context, actionClass, null, 0);
	}
	
	/**
	 * Start the action
	 * 
	 * @param context
	 *		the current context
	 * @param actionClass
	 * 		the action class 
	 * @param model
	 * 		the JSON model to pass as data
	 */
	public static void start(Context context, Class<? extends Action> actionClass, GenericJson model) {
		start(context, actionClass, EndpointsUtils.toBundle(model), 0);
	}
	
	/**
	 * Start the action
	 * 
	 * @param context
	 *		the current context
	 * @param actionClass
	 * 		the action class 
	 * @param bundle
	 * 		the data to pass
	 */
	public static void start(Context context, Class<? extends Action> actionClass, Bundle bundle) {
		start(context, actionClass, bundle, 0);
	}
	
	/**
	 * Start the action
	 * 
	 * @param context
	 *		the current context
	 * @param actionClass
	 * 		the action class 
	 * @param bundle
	 * 		the data to pass
	 * @param flags
	 * 		intent flags
	 */
	public static void start(Context context, Class<? extends Action> actionClass, Bundle bundle, int flags) {
		if (actionClass==null)
			throw new IllegalArgumentException("actionClass cannot be null");
		Intent intent = new Intent(context, ActionService.class);
		intent.setAction(actionClass.getSimpleName());
		intent.addFlags(flags);
		if(bundle!=null)
			intent.putExtras(bundle);
		context.startService(intent);
	}	
	
	/**
	 * Refire current activity with this action, if it is the one specified
	 * 
	 * @param activityClass
	 * 		the class of the activity that should match the current activity
	 */
	public boolean refireActivity(Class<? extends Activity> activityClass) {
		Activity activity = app.getCurrentActivity();
		if (activity!=null && activity.getClass()==activityClass) {
			Intent intent = new Intent(context, activityClass);
			intent.setAction(action);
			if (getIntent().getExtras()!=null)
				intent.putExtras(getIntent().getExtras());
			SharedPreferences prefs = context.getSharedPreferences(
					SolidalApp.class.getCanonicalName(), Context.MODE_PRIVATE);
			synchronized (prefs) {
				int intentId = prefs.getInt(Constants.INTENT, 0) + 1;
				intent.putExtra(Constants.INTENT, intentId);
			}
			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
			context.startActivity(intent);
			return true;
		}
		return false;
	}
		
	protected void reportCrash(String regId, String exception, Exception exc) {
		for (int i=0;i<3;i++)
			try {
				Crash crash = new Crash();
				crash.setTimestamp(
						new SimpleDateFormat(
								context.getString(R.string.log_dateformat))
									.format(new Date()));
				crash.setRegistrationId(regId);
				crash.setException(exception);
				if (exc!=null) {
					StringWriter w = new StringWriter();
					exc.printStackTrace(new PrintWriter(w));
					crash.setStackTrace(w.toString());
				}
				EndpointsUtils.buildEndpoint(Crashendpoint.class)
					.insertCrash(crash).execute();
				break;
			} catch(Exception e) {
				Log.e(getClass().getSimpleName(), "Cannot send crash", e);
				try {
					Thread.sleep(1000);
				} catch(InterruptedException ie) {}
			}
	}
				
}
